﻿package com.sgcc.nfc.lock.presentation.nfc

import android.nfc.Tag
import android.nfc.TagLostException
import android.nfc.tech.NfcA
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.sgcc.nfc.lock.core.nfc.NfcDeviceManager
import com.sgcc.nfc.lock.core.nfc.NfcDeviceManager.Control
import com.sgcc.nfc.lock.core.network.NetworkResult
import com.sgcc.nfc.lock.domain.model.NfcOperationParams
import com.sgcc.nfc.lock.domain.model.RoleType
import com.sgcc.nfc.lock.domain.model.Room
import com.sgcc.nfc.lock.domain.model.UserProfile
import com.sgcc.nfc.lock.domain.usecase.AddLockUseCase
import com.sgcc.nfc.lock.domain.usecase.CloseLockUseCase
import com.sgcc.nfc.lock.domain.usecase.FindLockByNfcIdUseCase
import com.sgcc.nfc.lock.domain.usecase.FindLockByNfcIdUseCase.LockAccessResult
import com.sgcc.nfc.lock.domain.usecase.GetProfileUseCase
import com.sgcc.nfc.lock.domain.usecase.GetRoomsUseCase
import com.sgcc.nfc.lock.domain.usecase.OpenLockUseCase
import com.sgcc.nfc.lock.core.nfc.NfcDeviceManager.Result as DeviceResult
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@HiltViewModel
class NfcOperationViewModel @Inject constructor(
    private val openLockUseCase: OpenLockUseCase,
    private val closeLockUseCase: CloseLockUseCase,
    private val addLockUseCase: AddLockUseCase,
    private val findLockByNfcIdUseCase: FindLockByNfcIdUseCase,
    private val getRoomsUseCase: GetRoomsUseCase,
    private val getProfileUseCase: GetProfileUseCase,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    enum class Mode { OPEN, CLOSE }

    private val mode: Mode = savedStateHandle.get<String>(EXTRA_ACTION)?.let { Mode.valueOf(it) } ?: Mode.OPEN
    private val presetRoomId: Long? = savedStateHandle.get<Long?>(EXTRA_ROOM_ID)
    private val expectedLockNfcId: String? = savedStateHandle.get<String>(EXTRA_LOCK_NFC_ID)
    private val lockPasswordKey: ByteArray = buildPasswordKey()

    private var locationManuallySelected = false
    private var lastProcessedTag: String? = null
    private var currentUser: UserProfile? = null
    private var currentUserLoaded = false
    private var isLoadingUser = false

    private val _uiState = MutableLiveData(
        NfcOperationUiState(
            mode = mode,
            statusText = initialStatusText(mode)
        )
    )
    val uiState: LiveData<NfcOperationUiState> = _uiState

    init {
        loadCurrentUser()
        loadRooms()
    }

    fun selectRoom(roomId: Long?) {
        val rooms = _uiState.value?.rooms.orEmpty()
        val room = rooms.firstOrNull { it.roomId == roomId }
        _uiState.updateState { state ->
            val target = room ?: state.selectedRoom
            val latitude = when {
                locationManuallySelected -> state.latitude
                target?.latitude != null -> target.latitude
                else -> state.latitude
            }
            val longitude = when {
                locationManuallySelected -> state.longitude
                target?.longitude != null -> target.longitude
                else -> state.longitude
            }
            state.copy(
                selectedRoom = target,
                latitude = latitude,
                longitude = longitude,
                message = null
            )
        }
    }

    fun updateLocation(latitude: Double, longitude: Double) {
        locationManuallySelected = true
        _uiState.updateState { state ->
            state.copy(
                latitude = latitude,
                longitude = longitude,
                message = null
            )
        }
    }

    fun clearMessage() {
        _uiState.updateState { it.copy(message = null) }
    }

    fun onTagDetected(tag: Tag) {
        val tagId = tag.id?.joinToString(separator = "") { "%02X".format(it) } ?: run {
            _uiState.updateState {
                it.copy(
                    statusText = "Unreadable tag, please retry",
                    message = "Unreadable tag, please retry",
                    result = false,
                    showAddLockPrompt = false,
                    pendingLockNfcId = null,
                    addLockInProgress = false
                )
            }
            return
        }
        val current = _uiState.value ?: return
        if (current.isProcessing || current.addLockInProgress) return
        if (tagId == lastProcessedTag && current.result == true && !current.showAddLockPrompt) return

        if (!currentUserLoaded) {
            if (!isLoadingUser) {
                loadCurrentUser()
            }
            _uiState.updateState {
                it.copy(
                    isProcessing = false,
                    statusText = "Loading account info, please retry shortly",
                    message = "Loading account info, please retry shortly",
                    result = false,
                    chargeProgress = null,
                    showAddLockPrompt = false,
                    pendingLockNfcId = null,
                    addLockInProgress = false
                )
            }
            return
        }

        expectedLockNfcId?.let { expected ->
            if (!expected.equals(tagId, ignoreCase = true)) {
                _uiState.updateState {
                    it.copy(
                        statusText = "Detected unexpected tag ",
                        message = "Please use tag  for this lock",
                        result = false,
                        showAddLockPrompt = false,
                        pendingLockNfcId = null,
                        addLockInProgress = false
                    )
                }
                return
            }
        }

        val room = current.selectedRoom
        if (room == null) {
            _uiState.updateState {
                it.copy(
                    statusText = "Please choose a room",
                    message = "Select a room before operating the lock",
                    result = false
                )
            }
            return
        }

        if (!hasPermissionForRoom(room)) {
            _uiState.updateState {
                it.copy(
                    statusText = "No permission for this lock",
                    message = "Current account is not allowed to operate this lock",
                    result = false,
                    showAddLockPrompt = false,
                    pendingLockNfcId = null,
                    addLockInProgress = false,
                    chargeProgress = null
                )
            }
            return
        }

        val latitude = current.latitude
        val longitude = current.longitude
        if (latitude == null || longitude == null) {
            _uiState.updateState {
                it.copy(
                    statusText = "Missing location information",
                    message = "Confirm current location or choose the room location",
                    result = false
                )
            }
            return
        }

        _uiState.updateState {
            it.copy(
                isProcessing = true,
                statusText = "Validating lock information...",
                message = null,
                result = null,
                lastTagId = null,
                chargeProgress = null,
                showAddLockPrompt = false,
                pendingLockNfcId = null,
                addLockInProgress = false
            )
        }

        viewModelScope.launch {
            when (val access = findLockByNfcIdUseCase(tagId)) {
                is LockAccessResult.Allowed -> {
                    val resolvedRoom = _uiState.value?.rooms
                        ?.firstOrNull { access.lock.roomId != null && it.roomId == access.lock.roomId }
                        ?: room
                    if (!hasPermissionForRoom(resolvedRoom)) {
                        _uiState.updateState {
                            it.copy(
                                isProcessing = false,
                                statusText = "No permission for this lock",
                                message = "Current account is not allowed to operate this lock",
                                result = false,
                                chargeProgress = null,
                                showAddLockPrompt = false,
                                pendingLockNfcId = null,
                                addLockInProgress = false
                            )
                        }
                        return@launch
                    }
                    lastProcessedTag = tagId
                    _uiState.updateState {
                        it.copy(
                            selectedRoom = resolvedRoom,
                            isProcessing = true,
                            statusText = "Detected , preparing to ",
                            message = null,
                            result = null,
                            lastTagId = tagId,
                            chargeProgress = 0,
                            showAddLockPrompt = false,
                            pendingLockNfcId = null,
                            addLockInProgress = false
                        )
                    }
                    when (val hardware = performHardwareOperation(tag, tagId)) {
                        is HardwareResult.Success -> {
                            handleBackendReport(tagId, resolvedRoom, latitude, longitude, hardware.deviceInfo)
                        }
                        is HardwareResult.Failure -> {
                            lastProcessedTag = null
                            _uiState.updateState {
                                it.copy(
                                    isProcessing = false,
                                    statusText = hardware.message,
                                    message = hardware.message,
                                    result = false,
                                    chargeProgress = null,
                                    showAddLockPrompt = false,
                                    pendingLockNfcId = null,
                                    addLockInProgress = false
                                )
                            }
                        }
                    }
                }
                LockAccessResult.NotFound -> {
                    lastProcessedTag = null
                    if (!hasPermissionForRoom(room)) {
                        _uiState.updateState {
                            it.copy(
                                isProcessing = false,
                                statusText = "No permission for this lock",
                                message = "Current account is not allowed to operate this lock",
                                result = false,
                                chargeProgress = null,
                                showAddLockPrompt = false,
                                pendingLockNfcId = null,
                                addLockInProgress = false
                            )
                        }
                        return@launch
                    }
                    _uiState.updateState {
                        it.copy(
                            isProcessing = false,
                            statusText = "Lock not found in system",
                            message = "No lock found for tag , please register it",
                            result = false,
                            chargeProgress = null,
                            showAddLockPrompt = true,
                            pendingLockNfcId = tagId,
                            addLockInProgress = false
                        )
                    }
                }
                is LockAccessResult.Forbidden -> {
                    lastProcessedTag = null
                    val hint = access.message ?: "Current account has no permission for this lock"
                    _uiState.updateState {
                        it.copy(
                            isProcessing = false,
                            statusText = hint,
                            message = hint,
                            result = false,
                            chargeProgress = null,
                            showAddLockPrompt = false,
                            pendingLockNfcId = null,
                            addLockInProgress = false
                        )
                    }
                }
                is LockAccessResult.Error -> {
                    lastProcessedTag = null
                    val hint = access.message ?: "Failed to validate lock information, please retry"
                    _uiState.updateState {
                        it.copy(
                            isProcessing = false,
                            statusText = hint,
                            message = hint,
                            result = false,
                            chargeProgress = null,
                            showAddLockPrompt = false,
                            pendingLockNfcId = null,
                            addLockInProgress = false
                        )
                    }
                }
            }
        }
    }

    private suspend fun handleBackendReport(
        tagId: String,
        room: Room,
        latitude: Double,
        longitude: Double,
        deviceInfo: NfcDeviceManager.DeviceInfo
    ) {
        val params = NfcOperationParams(
            lockNfcId = tagId,
            roomId = room.roomId,
            longitude = longitude,
            latitude = latitude,
            remark = buildRemark(room)
        )
        val response = when (mode) {
            Mode.OPEN -> openLockUseCase(params)
            Mode.CLOSE -> closeLockUseCase(params)
        }
        when (response) {
            is NetworkResult.Success -> {
                val successText = if (mode == Mode.OPEN) "Lock opened" else "Lock closed"
                _uiState.updateState {
                    it.copy(
                        isProcessing = false,
                        statusText = successText,
                        result = true,
                        message = "Operation completed successfully",
                        chargeProgress = null,
                        addLockInProgress = false
                    )
                }
            }
            is NetworkResult.Error -> {
                if (response.code == LOCK_NOT_EXIST_CODE) {
                    lastProcessedTag = null
                    _uiState.updateState {
                        it.copy(
                            isProcessing = false,
                            statusText = "Lock not found in system",
                            result = false,
                            message = "The lock is not registered, please register it first",
                            showAddLockPrompt = true,
                            pendingLockNfcId = tagId,
                            chargeProgress = null,
                            addLockInProgress = false
                        )
                    }
                } else {
                    val message = response.message ?: "Operation failed, please retry"
                    _uiState.updateState {
                        it.copy(
                            isProcessing = false,
                            statusText = message,
                            message = message,
                            result = false,
                            chargeProgress = null,
                            showAddLockPrompt = false,
                            pendingLockNfcId = null,
                            addLockInProgress = false
                        )
                    }
                }
            }
            NetworkResult.Loading -> Unit
        }
    }

    private suspend fun performHardwareOperation(tag: Tag, tagId: String): HardwareResult {
        val nfcA = NfcA.get(tag) ?: return HardwareResult.Failure("Unsupported tag protocol")
        val manager = NfcDeviceManager(nfcA)
        return try {
            val (initResult, device) = manager.init()
            if (initResult != DeviceResult.OK || device == null) {
                return HardwareResult.Failure("Failed to initialise the lock, please retry")
            }
            val (infoResult, info) = manager.getInfo(device)
            if (infoResult != DeviceResult.OK || info == null) {
                return HardwareResult.Failure("Failed to read lock info, please retry")
            }
            val workKey = manager.deriveWorkKey(lockPasswordKey, info)
            repeat(MAX_CHARGE_ATTEMPTS) { attempt ->
                val (chargeResult, chargeLevel) = manager.getChargeLevel(device, workKey)
                if (chargeResult != DeviceResult.OK || chargeLevel == null) {
                    return HardwareResult.Failure("Failed to get charging progress, please retry")
                }
                _uiState.updateState {
                    it.copy(
                        chargeProgress = chargeLevel.level,
                        statusText = "Charging % (attempt /)"
                    )
                }
                if (chargeLevel.complete) {
                    val controlResult = manager.control(device, workKey, controlCommand(), NFC_USER_NAME)
                    return if (controlResult == DeviceResult.OK) {
                        HardwareResult.Success(info)
                    } else {
                        HardwareResult.Failure("Lock refused the command, please retry")
                    }
                }
                delay(CHARGE_INTERVAL_MS)
            }
            HardwareResult.Failure("Charging timed out, please retry")
        } catch (ex: TagLostException) {
            HardwareResult.Failure("Tag lost during operation, please retry")
        } catch (ex: Exception) {
            HardwareResult.Failure(ex.message ?: "Unexpected error")
        } finally {
            manager.close()
        }
    }

    fun addPendingLock() {
        val state = _uiState.value ?: return
        val tagId = state.pendingLockNfcId ?: return
        val room = state.selectedRoom
        val roomId = room?.roomId
        if (roomId == null) {
            _uiState.updateState {
                it.copy(message = "Please choose a room before registering a lock")
            }
            return
        }

        _uiState.updateState {
            it.copy(
                addLockInProgress = true,
                message = null,
                statusText = "Registering lock...",
                isProcessing = false,
                chargeProgress = null
            )
        }

        viewModelScope.launch {
            when (val result = addLockUseCase(tagId, roomId)) {
                is NetworkResult.Success -> {
                    lastProcessedTag = null
                    _uiState.updateState {
                        it.copy(
                            addLockInProgress = false,
                            showAddLockPrompt = false,
                            pendingLockNfcId = null,
                            statusText = "Lock registered, please tap the tag again to ",
                            message = "Lock registered successfully. Tap the tag again to continue",
                            result = null,
                            chargeProgress = null
                        )
                    }
                }
                is NetworkResult.Error -> {
                    val message = result.message ?: "Failed to register the lock, please retry"
                    _uiState.updateState {
                        it.copy(
                            addLockInProgress = false,
                            statusText = message,
                            message = message,
                            result = false,
                            chargeProgress = null
                        )
                    }
                }
                NetworkResult.Loading -> Unit
            }
        }
    }

    private fun loadRooms() {
        viewModelScope.launch {
            when (val result = getRoomsUseCase(1, DEFAULT_ROOM_PAGE_SIZE)) {
                is NetworkResult.Success -> {
                    val rooms = result.data.data
                    _uiState.updateState { state ->
                        val selected = rooms.firstOrNull { it.roomId == presetRoomId }
                            ?: state.selectedRoom
                            ?: rooms.firstOrNull()
                        val latitude = when {
                            locationManuallySelected -> state.latitude
                            selected?.latitude != null -> selected.latitude
                            else -> state.latitude
                        }
                        val longitude = when {
                            locationManuallySelected -> state.longitude
                            selected?.longitude != null -> selected.longitude
                            else -> state.longitude
                        }
                        state.copy(
                            rooms = rooms,
                            selectedRoom = selected,
                            latitude = latitude,
                            longitude = longitude,
                            message = null
                        )
                    }
                }
                is NetworkResult.Error -> {
                    _uiState.updateState {
                        it.copy(message = result.message ?: "Failed to load rooms")
                    }
                }
                NetworkResult.Loading -> Unit
            }
        }
    }

    private fun buildRemark(room: Room): String =
        "NFC--".take(100)

    private fun initialStatusText(mode: Mode): String =
        if (mode == Mode.OPEN) "Hold phone close to the lock to open" else "Hold phone close to the lock to close"

    private fun MutableLiveData<NfcOperationUiState>.updateState(transform: (NfcOperationUiState) -> NfcOperationUiState) {
        val current = value ?: NfcOperationUiState(mode = mode, statusText = initialStatusText(mode))
        value = transform(current)
    }

    private fun operationVerb(): String = if (mode == Mode.OPEN) "open" else "close"

    private fun controlCommand(): Control = if (mode == Mode.OPEN) Control.UNLOCK else Control.LOCK

    private fun buildPasswordKey(): ByteArray {
        val raw = NFC_LOCK_PASSWORD.encodeToByteArray()
        return if (raw.size >= 16) {
            raw.copyOf(16)
        } else {
            ByteArray(16).also { buffer ->
                raw.copyInto(buffer)
            }
        }
    }

    private fun loadCurrentUser() {
        if (isLoadingUser || currentUserLoaded) return
        isLoadingUser = true
        viewModelScope.launch {
            when (val result = getProfileUseCase()) {
                is NetworkResult.Success -> {
                    currentUser = result.data
                    currentUserLoaded = true
                }
                is NetworkResult.Error -> {
                    currentUser = null
                    currentUserLoaded = false
                }
                NetworkResult.Loading -> Unit
            }
            isLoadingUser = false
        }
    }

    private fun hasPermissionForRoom(room: Room?): Boolean {
        val user = currentUser ?: return false
        val role = RoleType.fromValue(user.roleType) ?: return false
        if (role == RoleType.SUPER_ADMIN || role == RoleType.PROVINCE_ADMIN) {
            return true
        }
        val userRegionId = user.regionId ?: return false
        val roomRegionId = room?.regionId ?: return false
        return userRegionId == roomRegionId
    }

    private sealed class HardwareResult {
        data class Success(val deviceInfo: NfcDeviceManager.DeviceInfo) : HardwareResult()
        data class Failure(val message: String) : HardwareResult()
    }

    companion object {
        const val EXTRA_ROOM_ID = "roomId"
        const val EXTRA_ACTION = "action"
        const val EXTRA_LOCK_NFC_ID = "lockNfcId"

        private const val DEFAULT_ROOM_PAGE_SIZE = 200
        private const val LOCK_NOT_EXIST_CODE = 4001

        private const val MAX_CHARGE_ATTEMPTS = 40
        private const val CHARGE_INTERVAL_MS = 200L
        private const val NFC_LOCK_PASSWORD = "123456"
        private const val NFC_USER_NAME = "user"
    }
}




